#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "rng.h"
#include "cursor.h"
/*
 * "2048.h" by Loveaabb<loveaabb2@163.com>
 * License: MIT
*/


typedef enum{NONE, MAXNUM, SCORE} Vicmode;

//#define Swap(a, b) (a)^=(b)^=(a)^=(b)

void Swap(int* a, int* b);

double log10(double x);
double floor(double x);

/* def of Game */
typedef struct{
    int board[4][4];
    int score;
    Vicmode vicmode;
    int condition;
} Game;

Game Game_init(Vicmode vicmode, int condition);
int move_line(int* p1, int* p2, int* p3, int* p4, int* toScore);
int Game_moveUp(Game* game);
int Game_moveDown(Game* game);
int Game_moveLeft(Game* game);
int Game_moveRight(Game* game);
void Game_printBoard(Game* game);
int Game_getNum(Game* game);
int Game_addNum(Game* game, int num);
int Game_isDead(Game* game);
int Game_isVictory(Game* game);


/* Game member function definitions */

Game Game_init(Vicmode vicmode, int condition){
    Game tg;
    memset(tg.board, 0, 16 * sizeof(int));
    tg.score = 0;
    int numToInsert = 2, px = rng(0, 3), py = rng(0, 3);
    for(int i = 0; i < 2; i++){
        numToInsert = 2;
        if(rng(1, 10) == 1)numToInsert = 4;
        while(tg.board[py][px]){
            px = rng(0, 3);
            py = rng(0, 3);
        }
        tg.board[py][px] = numToInsert;
    }
    tg.vicmode = vicmode;
    tg.condition = condition;
    return tg;
}


int move_line(int* p1, int* p2, int* p3, int* p4, int* toScore){
    int score = 0, moved = 0;
    int* ptrs[] = {p1, p2, p3, p4};
    int** cur = ptrs + 2;
    int** ncur = ptrs;
    while(cur >= ptrs){
        ncur = cur;
        while(ncur >= ptrs && ncur < ptrs + 3){
            if(**(ncur + 1) == 0 && **ncur){
                Swap(*ncur, *(ncur + 1));
                ncur++;
                moved = 1;
                continue;
            }

            if(**(ncur + 1) == -1 && **ncur){
                **(ncur + 1) = **ncur;
                **ncur = 0;
                break;
            }

            if(**(ncur + 1) == **ncur && **ncur){
                score += (**ncur) * 2;
                **(ncur + 1) *= 2;
                **(ncur) = -1;
                moved = 1;
                break;
            }

            break;
        }
        cur--;
    }
    cur = ptrs + 2;
    for(int i = 0; i < 4; i++){
        if(**(ptrs + i) == -1) **(ptrs + i) = 0;
    }
    *toScore = score;
    return moved;
}


int Game_moveUp(Game* game){
    int tscore = 0, moved = 0, scorevar = 0;
    for(int i = 0; i < 4; i++){
        moved |= move_line(&(game->board[3][i]), &(game->board[2][i]), &(game->board[1][i]), &(game->board[0][i]), &scorevar);
        tscore += scorevar;
    }
    game->score += tscore;
    return moved;
}

int Game_moveDown(Game* game){
    int tscore = 0, moved = 0, scorevar = 0;
    for(int i = 0; i < 4; i++){
        moved |= move_line(&(game->board[0][i]), &(game->board[1][i]), &(game->board[2][i]), &(game->board[3][i]), &scorevar);
        tscore += scorevar;
    }
    game->score += tscore;
    return moved;
}

int Game_moveLeft(Game* game){
    int tscore = 0, moved = 0, scorevar = 0;
    for(int i = 0; i < 4; i++){
        moved |= move_line(&(game->board[i][3]), &(game->board[i][2]), &(game->board[i][1]), &(game->board[i][0]), &scorevar);
        tscore += scorevar;
    }
    game->score += tscore;
    return moved;
}

int Game_moveRight(Game* game){
    int tscore = 0, moved = 0, scorevar = 0;
    for(int i = 0; i < 4; i++){
        moved |= move_line(&(game->board[i][0]), &(game->board[i][1]), &(game->board[i][2]), &(game->board[i][3]), &scorevar);
        tscore += scorevar;
    }
    game->score += tscore;
    return moved;
}

int Game_getNum(Game* game){
    int res = 0;
    for(int y = 0; y < 4; y++)
        for(int x = 0; x < 4; x++){
            if(game->board[y][x])res++;
        }
    return res;
}

int Game_addNum(Game* game, int num){
    int gn = Game_getNum(game);
    if(gn >= 16) return 0;
    int pos = rng(1, 16 - gn), cpos = 1;
    int* bptr = game->board[0];
    while(bptr <= game->board[0] + 15){
        if(*bptr){
            bptr++;
            continue;
        }
        if(cpos == pos){
            *bptr = num;
            return 1;
        }
        bptr++;
        cpos++;
    }
    return 0;
}

void Game_printBoard(Game* game){
    static const char emptyBoard[] =
    "\
---------------------------------\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
---------------------------------\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
---------------------------------\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
---------------------------------\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
|       |       |       |       |\n\
---------------------------------\n\
    ";
    hSetCursor(0, 0);
    printf(emptyBoard);
    int offset, cnum;
    for(int y = 0; y < 4; y++){
        for(int x = 0; x < 4; x++){
            cnum = game->board[y][x];
            if(cnum){
                offset = floor(log10(cnum)) / 2;
                hSetCursor(x * 8 + 4 - offset, y * 4 + 2);
                printf("%d", cnum);
            }
        }
    }
    hSetCursor(0, 17);
    printf("                                                 ");
    hSetCursor(0, 17);
    if(game->vicmode == MAXNUM){
        printf("Get %d to win! |", game->condition);
    }
    if(game->vicmode == SCORE){
        printf("Target Score mode | Score: %d / %d", game->score, game->condition);
    }
    else{
    printf(" Score: %d", game->score);
    }
    hSetCursor(0, 21);

}

int Game_isDead(Game* game){
    if(Game_getNum(game) < 16) return 0;
    for(int y = 0; y < 4; y++){
        for(int x = 0; x < 4; x++){
            if(x + 1 < 4 && game->board[y][x] == game->board[y][x + 1] || y + 1 < 4 && game->board[y][x] == game->board[y + 1][x])return 0;
        }
    }
    return 1;
}

int Game_isVictory(Game* game){
    if(game->vicmode == MAXNUM){
        for(int y = 0; y < 4; y++){
            for(int x = 0; x < 4; x++){
                if(game->board[y][x] >= game->condition) return 1;
            }
        }
    }
    if(game->vicmode == SCORE){
        if(game->score >= game->condition)return 1;
    }
    return 0;
}
/* end of Game */

/* Global function definitions as follow */

void Swap(int* a, int* b){
    (*a) ^= (*b) ^= (*a) ^= (*b);
}
